package com.markhsiu.common.memory;

import java.nio.ByteBuffer;

/**
 * 简单封装下，参考netty和ByteArrayOutputStream
 * @author Mark Hsiu
 *
 */
public final class HeapByteBuff extends ByteBuff{

	private static final int def_capacity = 1024;
	private int capacity = def_capacity;
	private ByteBuffer buffer;
	private int length;

	public HeapByteBuff() {
		buffer = ByteBuffer.allocate(capacity);
	}

	public HeapByteBuff(int capacity) {
		this.capacity = capacity;
		this.buffer = ByteBuffer.allocate(capacity);
	}

	private HeapByteBuff(ByteBuffer buffer) {
		this.buffer = buffer;
	}

	public ByteBuff putInt(int value) {
		this.ensureWritable(4);
		this.buffer.putInt(value);
		this.length += 4;
		return this;
	}
	
	public HeapByteBuff put(byte value) {
		this.ensureWritable(1);
		this.buffer.put(value);
		this.length += 1;
		return this;
	}

	public HeapByteBuff put(byte[] src) {
		this.ensureWritable(src.length);
		this.buffer.put(src);
		this.length += src.length;
		return this;
	}
	
	public HeapByteBuff put(byte[] src, int offset, int length){
		this.ensureWritable(length);
		this.buffer.put(src,offset,length);
		this.length += length;
		return this;
	}
	
	public byte[] data() {
		this.buffer.flip();
		byte[] bytes = new byte[buffer.remaining()];
		this.buffer.get(bytes, 0, bytes.length);
		return bytes;
	}

	public void clear() {
		this.buffer.clear();
		length = 0;
	}
	
	public int length() {
		return length;
	}
	
	public int capacity() {
		return capacity;
	}

	public int capacity(int newCapacity) {
		this.capacity = newCapacity;
		if(this.buffer != null){
			ByteBuffer src = this.buffer;
			src.flip();
			this.buffer = ByteBuffer.allocate(this.capacity);
			this.buffer.put(src);
		}
		
		return capacity;
	}

	public int writableBytes() {
		return capacity() - writerIndex();
	}
	
	public int writerIndex(){
		return buffer.position();
	}


	public HeapByteBuff ensureWritable(int minWritableBytes) {
		if (minWritableBytes < 0) {
			throw new IllegalArgumentException(
					String.format("minWritableBytes: %d (expected: >= 0)", minWritableBytes));
		}

		
		if (minWritableBytes <= writableBytes()) {
			return this;
		}

		// Normalize the current capacity to the power of 2.
		int newCapacity = calculateNewCapacity(writerIndex() + minWritableBytes);
		
		// Adjust to the new capacity.
		capacity(newCapacity);
		return this;
	}

	private int calculateNewCapacity(int minNewCapacity) {
		final int maxCapacity = this.capacity;
		final int threshold = 1048576 * 4; // 4 MiB page

		if (minNewCapacity == threshold) {
			return threshold;
		}

		// If over threshold, do not double but just increase by threshold.
		if (minNewCapacity > threshold) {
			int newCapacity = minNewCapacity / threshold * threshold;
			if (newCapacity > maxCapacity - threshold) {
				newCapacity = maxCapacity;
			} else {
				newCapacity += threshold;
			}
			return newCapacity;
		}

		// Not over threshold. Double up to 4 MiB, starting from 64.
		int newCapacity = 64;
		while (newCapacity < minNewCapacity) {
			newCapacity <<= 1;
		}
		return newCapacity;
	}

	public static HeapByteBuff wrap(byte[] array) {
		return new HeapByteBuff(ByteBuffer.wrap(array));
	}

	public static HeapByteBuff allocate(int capacity) {
		return new HeapByteBuff(capacity);
	}

	 

	@Override
	void _put(int index, byte b) {
		buffer.put(index, b);
	}

	@Override
	byte _get(int index) {
		return buffer.get(index);
	}
}
